import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.extensions.PluginId;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;

/**
 * @author Ramer
 * @version 02/13/2018
 */
public class MainGenerator extends AnAction {
  private static String lineSeparator = System.getProperty("line.separator");
  private static String pathSeparator = File.separator;

  private static void foo(ClassLoader classLoader) throws Exception {
    System.out.println(
        "===================================================================================");
    System.out.println(
        "=======================    admin-springdata代码生成器idea 插件 v1.0.0    ====================");
    System.out.println(
        "=======================    Author Ramer                        ====================");
    System.out.println(
        "===================================================================================");
    System.out.println();

    String classPath,
        domainPath,
        aliaName,
        description,
        isCopy,
        savingPathRepository = null,
        savingPathService = null,
        savingPathServiceImpl = null,
        savingPathController = null,
        savingPathValidator = null,
        savingPathPoJo = null,
        savingPathRequest = null,
        savingPathResponse = null;

    // 读取配置文件信息
    //    final String path =
    //        MainGenerator.class.getProtectionDomain().getCodeSource().getLocation().getPath();
    String path = classLoader.getResource("config.ini").getPath();
    classLoaderG = classLoader;
    //    path = path.substring(0, path.indexOf("/config.ini"));

    Properties properties = new Properties();
    File configFile = new File(path.substring(0, path.lastIndexOf("/")).concat("/config.ini"));
    // 文件优先级: resources/config/config.ini > config.ini
    final File configFilePriority =
        new File(path.substring(0, path.lastIndexOf("/")).concat("/config/config.ini"));
    if (configFilePriority.exists()) {
      configFile = configFilePriority;
    }
    // 基础包路径
    String basePath;
    if (configFile.exists()) {
      properties.load(
          new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8));
      basePath = properties.getProperty("basePath");
      domainPath = properties.getProperty("domainPath");
      classPath = properties.getProperty("classPath");
      aliaName = properties.getProperty("aliaName");
      description = properties.getProperty("description");
      isCopy = properties.getProperty("isCopy");
      if (isCopy.equals("y")) {
        savingPathRepository = properties.getProperty("savingPathRepository");
        savingPathService = properties.getProperty("savingPathService");
        savingPathServiceImpl =
            Objects.isNull(savingPathService)
                ? null
                : savingPathService.concat(pathSeparator).concat("impl");
        savingPathController = properties.getProperty("savingPathController");
        savingPathValidator = properties.getProperty("savingPathValidator");
        savingPathPoJo = properties.getProperty("savingPathPoJo");
        savingPathRequest = properties.getProperty("savingPathRequest");
        savingPathResponse = properties.getProperty("savingPathResponse");
      }
    } else {
      System.out.println(" 请输入项目包路径: 例如: org.ramer.demo");
      Scanner scanner = new Scanner(System.in);
      basePath = scanner.next();
      System.out.println(
          " 请输入domain对象的包路径: 例如: org.ramer.demo.entity.domain.manage.system.Manager");
      domainPath = scanner.next();
      System.out.println(" 请输入domain对象的class目录: 例如: D:/workspace/admin-springdata/target/classes");
      classPath = scanner.next();
      System.out.println(" 请输入对象的别名: 例如: manager");
      aliaName = scanner.next();
      System.out.println(" 请输入对象的中文描述: 例如: 管理员");
      description = scanner.next();
      System.out.println(" 复制到指定路径 ?(y/n)");
      isCopy = scanner.next();

      while (!isCopy.equals("y") && !isCopy.equals("n")) {
        System.out.println(" 输入有误,请重新输入(y/n)");
      }
      if (isCopy.equalsIgnoreCase("y")) {
        System.out.println(
            " 请输入repository保存路径: 例如: D:/workspace/admin-springdata/src/main/java/org/ramer/admin/repository");
        savingPathRepository = scanner.next();
        System.out.println(
            " 请输入service保存路径: D:/workspace/admin-springdata/src/main/java/org/ramer/admin/service");
        savingPathService = scanner.next();
        savingPathServiceImpl =
            Objects.isNull(savingPathService)
                ? null
                : savingPathService.concat(pathSeparator).concat("impl");
        System.out.println(
            " 请输入controller保存路径: 例如: D:/workspace/admin-springdata/src/main/java/org/ramer/admin/controller");
        savingPathController = scanner.next();
        System.out.println(
            " 请输入validator保存路径: 例如: D:/workspace/admin-springdata/src/main/java/org/ramer/admin/validator");
        savingPathValidator = scanner.next();
        System.out.println(
            " 请输入pojo保存路径: 例如: D:/workspace/admin-springdata/src/main/java/org/ramer/admin/entity/pojo");
        savingPathPoJo = scanner.next();
        System.out.println(
            " 请输入request保存路径: 例如: D:/workspace/admin-springdata/src/main/java/org/ramer/admin/entity/request");
        savingPathRequest = scanner.next();
        System.out.println(
            " 请输入response保存路径: 例如: D:/workspace/admin-springdata/src/main/java/org/ramer/admin/entity/response");
        savingPathResponse = scanner.next();
      }
    }

    // domain类名
    final String domainName = domainPath.substring(domainPath.lastIndexOf(".") + 1);
    String moduleName = domainPath.substring(0, domainPath.indexOf(".entity"));
    // 长度相同说明没有子模块
    moduleName =
        moduleName.length() == basePath.length() ? "" : moduleName.substring(basePath.length());

    final int subDirFrom = domainPath.indexOf("domain.") + 6;
    final int subDirTo = domainPath.indexOf(domainName) - 1;
    final String subDir = subDirFrom == subDirTo ? "" : domainPath.substring(subDirFrom, subDirTo);

    generateRepository(
        basePath, moduleName, domainName, subDir, aliaName, description, savingPathRepository);
    generateService(
        basePath, moduleName, domainName, subDir, aliaName, description, savingPathService);
    generateServiceImpl(
        basePath, moduleName, domainName, subDir, aliaName, description, savingPathServiceImpl);
    generateController(
        basePath, moduleName, domainName, subDir, aliaName, description, savingPathController);
    generateValidator(
        basePath, moduleName, domainName, subDir, aliaName, description, savingPathValidator);
    generatePoJo(
        basePath,
        moduleName,
        domainName,
        subDir,
        aliaName,
        description,
        domainPath,
        classPath,
        savingPathPoJo);
    generateRequest(
        basePath,
        moduleName,
        domainName,
        subDir,
        aliaName,
        description,
        domainPath,
        classPath,
        savingPathRequest);
    generateResponse(
        basePath,
        moduleName,
        domainName,
        subDir,
        aliaName,
        description,
        domainPath,
        classPath,
        savingPathResponse);
    System.out.println(
        "=======================               执行完成 !!!             ====================");
  }

  public static void main(String[] args) throws Exception {
    foo(null);
  }

  private static void generateResponse(
      String basePath,
      String moduleName,
      String domainName,
      String subDir,
      String aliaName,
      String description,
      String domainPath,
      final String classPath,
      String savingPath)
      throws Exception {
    generateWithField(
        basePath,
        moduleName,
        domainName,
        subDir,
        aliaName,
        description,
        domainPath,
        classPath,
        savingPath,
        "Response");
  }

  private static void generateRequest(
      String basePath,
      String moduleName,
      String domainName,
      String subDir,
      String aliaName,
      String description,
      String domainPath,
      final String classPath,
      String savingPath)
      throws Exception {
    generateWithField(
        basePath,
        moduleName,
        domainName,
        subDir,
        aliaName,
        description,
        domainPath,
        classPath,
        savingPath,
        "Request");
  }

  private static void generatePoJo(
      String basePath,
      String moduleName,
      String domainName,
      String subDir,
      String aliaName,
      String description,
      String domainPath,
      final String classPath,
      String savingPath)
      throws Exception {
    generateWithField(
        basePath,
        moduleName,
        domainName,
        subDir,
        aliaName,
        description,
        domainPath,
        classPath,
        savingPath,
        "PoJo");
  }

  private static void generateValidator(
      String basePath,
      String moduleName,
      String domainName,
      String subDir,
      String aliaName,
      String description,
      String savingPath)
      throws Exception {
    generateWithNoField(
        basePath, moduleName, domainName, subDir, aliaName, description, savingPath, "Validator");
  }

  private static void generateController(
      String basePath,
      String moduleName,
      String domainName,
      String subDir,
      String aliaName,
      String description,
      String savingPath)
      throws Exception {
    generateWithNoField(
        basePath, moduleName, domainName, subDir, aliaName, description, savingPath, "Controller");
  }

  private static void generateServiceImpl(
      String basePath,
      String moduleName,
      String domainName,
      String subDir,
      String aliaName,
      String description,
      String savingPath)
      throws Exception {
    generateWithNoField(
        basePath, moduleName, domainName, subDir, aliaName, description, savingPath, "ServiceImpl");
  }

  private static void generateService(
      String basePath,
      String moduleName,
      String domainName,
      String subDir,
      String aliaName,
      String description,
      String savingPath)
      throws Exception {
    generateWithNoField(
        basePath, moduleName, domainName, subDir, aliaName, description, savingPath, "Service");
  }

  private static void generateRepository(
      String basePath,
      String moduleName,
      String domainName,
      String subDir,
      String aliaName,
      String description,
      String savingPath)
      throws Exception {
    generateWithNoField(
        basePath, moduleName, domainName, subDir, aliaName, description, savingPath, "Repository");
  }

  private static File copyTemplateFile(final String domainName, final String suffix)
      throws IOException {
    final InputStream inputStream =
        classLoaderG.getResourceAsStream("/generator-template/".concat(suffix).concat(".java"));
    //        ClassLoader.class.getResourceAsStream(
    //            "/generator-template/".concat(suffix).concat(".java"));
    final Path path = Files.createTempFile(domainName.concat(suffix), ".java");
    File file = path.toFile();
    file.deleteOnExit();
    if (Objects.isNull(inputStream)) {
      System.out.println(" 生成 ".concat(suffix).concat(" 失败 : 模板文件不存在"));
      return null;
    }
    Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING);
    return file;
  }

  private static StringBuilder loadFile(final File file) {
    StringBuilder stringBuilder = new StringBuilder();
    String lineStr;
    try (FileInputStream fileInputStream = new FileInputStream(file);
        InputStreamReader inputStreamReader =
            new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
        BufferedReader reader = new BufferedReader(inputStreamReader)) {
      while ((lineStr = reader.readLine()) != null) {
        stringBuilder.append(lineStr).append(lineSeparator);
      }
    } catch (Exception e) {
      System.err.println(" 载入文件失败: ".concat(e.getMessage()));
    }
    return stringBuilder;
  }

  private static void generateWithNoField(
      final String basePath,
      final String moduleName,
      final String domainName,
      final String subDir,
      final String aliaName,
      final String description,
      final String savingPath,
      final String suffix)
      throws IOException {
    File file = copyTemplateFile(domainName, suffix);
    if (file == null) return;
    StringBuilder stringBuilder = loadFile(file);
    writeFile(
        basePath,
        moduleName,
        domainName,
        subDir,
        aliaName,
        description,
        savingPath,
        suffix,
        file,
        stringBuilder,
        new StringBuilder());
  }

  private static void generateWithField(
      final String basePath,
      final String moduleName,
      final String domainName,
      final String subDir,
      final String aliaName,
      final String description,
      final String domainPath,
      final String classPath,
      final String savingPath,
      final String suffix)
      throws IOException {
    File file = copyTemplateFile(domainName, suffix);
    if (file == null) return;
    StringBuilder stringBuilder = loadFile(file);
    final StringBuilder fieldBuilder = new StringBuilder();
    buildField(domainPath, classPath, fieldBuilder);
    writeFile(
        basePath,
        moduleName,
        domainName,
        subDir,
        aliaName,
        description,
        savingPath,
        suffix,
        file,
        stringBuilder,
        fieldBuilder);
  }

  private static void buildField(
      final String domainPath, final String classPath, final StringBuilder fieldBuilder) {
    Class clazz;
    try {
      clazz =
          new URLClassLoader(new URL[] {new File(classPath).toURI().toURL()}).loadClass(domainPath);
    } catch (MalformedURLException | ClassNotFoundException e) {
      System.err.println("class 文件不存在: ".concat(domainPath));
      return;
    }
    final Map<Boolean, List<Field>> collect =
        Stream.of(clazz.getDeclaredFields())
            .filter(field -> Modifier.isPrivate(field.getModifiers()))
            .collect(Collectors.groupingBy(field -> field.getType().getName().contains("java.")));
    // 引用对应的字段名
    List<String> referenceNames = new ArrayList<>();
    Optional.ofNullable(collect.get(true))
        .orElseGet(ArrayList::new)
        .forEach(
            field -> {
              if (field.getType().getName().contains("List")) {
                fieldBuilder
                    .append(lineSeparator)
                    .append("  @ApiModelProperty(value = \"")
                    .append(field.getName())
                    .append("\")")
                    .append(lineSeparator)
                    .append("  private List<Long> ")
                    .append(field.getName())
                    .append("Ids;")
                    .append(lineSeparator);
              } else {
                final String simpleName = field.getType().getSimpleName();
                fieldBuilder
                    .append(lineSeparator)
                    .append("  @ApiModelProperty(value = \"")
                    .append(simpleName)
                    .append("\")")
                    .append(lineSeparator)
                    .append("  private ")
                    .append(simpleName)
                    .append(" ")
                    .append(field.getName())
                    .append(";")
                    .append(lineSeparator);
                if (simpleName.equals("Long")) {
                  referenceNames.add(field.getName());
                }
              }
            });
    Optional.ofNullable(collect.get(false)).orElseGet(ArrayList::new).stream()
        .filter(field -> !referenceNames.contains(field.getName().concat("Id")))
        .forEach(
            field ->
                fieldBuilder
                    .append(lineSeparator)
                    .append("  @ApiModelProperty(value = \"")
                    .append(field.getName())
                    .append("\")")
                    .append(lineSeparator)
                    .append("  private Long ")
                    .append(field.getName())
                    .append("Id")
                    .append(";")
                    .append(lineSeparator));
  }

  private static void writeFile(
      final String basePath,
      final String moduleName,
      final String domainName,
      final String subDir,
      final String aliaName,
      final String description,
      final String savingPath,
      final String suffix,
      final File file,
      final StringBuilder stringBuilder,
      final StringBuilder fieldBuilder)
      throws IOException {
    final String content =
        stringBuilder
            .toString()
            .replace("${basePath}", basePath)
            .replace("${moduleName}", moduleName)
            .replace("${name}", domainName)
            .replace("${alia}", aliaName)
            .replace("${description}", description)
            .replace("${subDir}", subDir)
            .replace("${subDirRequest}", subDir.replace(".", "/"))
            .replace("${fieldList}", fieldBuilder.toString());
    RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
    while (randomAccessFile.getFilePointer() < randomAccessFile.length()) {
      randomAccessFile.setLength(0);
      randomAccessFile.seek(0);
      randomAccessFile.write(content.getBytes(StandardCharsets.UTF_8));
    }
    if (!Objects.isNull(savingPath) && !"".equals(savingPath)) {
      if (!new File(savingPath).exists() && !new File(savingPath).mkdirs()) {
        System.out.println(" 生成 ".concat(suffix).concat(" 失败 : 目录创建失败"));
        return;
      }
      final String filePath =
          savingPath.concat(pathSeparator).concat(domainName).concat(suffix).concat(".java");
      Files.copy(file.toPath(), Paths.get(filePath), StandardCopyOption.REPLACE_EXISTING);
      System.out.println(" 生成 ".concat(suffix).concat(" 成功 : ").concat(filePath));
    } else {
      final File savingFile = new File(domainName.concat(suffix).concat(".java"));
      Files.copy(file.toPath(), Paths.get(savingFile.toURI()), StandardCopyOption.REPLACE_EXISTING);
      System.out.println(
          " 生成 ".concat(suffix).concat(" 成功 : ").concat(savingFile.getAbsolutePath()));
    }
  }

  /** 驼峰转下划线. */
  public static String camelToSnake(String str) {
    final char[] chars = str.toCharArray();
    StringBuilder stringBuilder = new StringBuilder();
    for (char ch : chars) {
      stringBuilder.append(Character.isUpperCase(ch) ? "_" + Character.toLowerCase(ch) : ch);
    }
    return stringBuilder.toString();
  }

  private static ClassLoader classLoaderG;

  @Override
  public void actionPerformed(@NotNull final AnActionEvent event) {
    //    event.getClass().getResource("config.ini");
    final PluginId pluginId = PluginId.getId("com.your.company.unique.plugin.id");
    final IdeaPluginDescriptor pluginDescriptor = PluginManager.getPlugin(pluginId);
    System.out.println(" : " + "开始运行插件");
    if (pluginDescriptor == null) {}
    System.out.println(" : " + "开始运行插件");
    try {
      foo(pluginDescriptor.getPluginClassLoader());
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
